package calao.client.ui;

import calao.client.Desktop;
import calao.client.i18n.Text;

import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ChangeListenerCollection;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.HasFocus;
import com.google.gwt.user.client.ui.SourcesChangeEvents;
import com.google.gwt.user.client.ui.SourcesTableEvents;
import com.google.gwt.user.client.ui.TableListener;
import com.google.gwt.user.client.ui.Widget;

public abstract class EditableGrid 
extends DataGrid
implements TableListener, ClickListener, SourcesChangeEvents, Validatable, Editable
{

	// TODO what if refresh happens while a cell is being edited ?
	
	final static String STYLE_NAME = "calao-EditableGrid";
	final static int DOUBLE_CLICK_MILLIS = 500;

	final static Text text = Desktop.getDesktop().text;
	
	final ChangeListenerCollection listeners = new ChangeListenerCollection();
	
	final SpacerPanel panel = new SpacerPanel(SpacerPanel.VERTICAL);
	final SpacerPanel buttonsPanel = new SpacerPanel(SpacerPanel.HORIZONTAL);
	
	final Button validateButton = createButton(text.validate(), false);
	final Button cancelButton = createButton(text.cancel(), false);
	final Button addButton = createButton(text.add(), canAddRow());
	final Button removeButton = createButton(text.remove(), false);

	long lastClickTime;

	int clickedRow = -1;
	int clickedCol = -1;
	int selectedRow = -1;
	
	boolean editEnabled;
	int editorCount = 0;
	Widget[] editors;
	
	boolean newRowValidated = true;

	public EditableGrid(int columns, boolean editEnabled)
	{
		super(columns);
		panel.add(grid);
		initWidget(panel);
		grid.addTableListener(this);
		addStyleName(STYLE_NAME);
		panel.add(buttonsPanel);
		setEditEnabled(editEnabled);
	}
	
	public EditableGrid(int columns)
	{
		this(columns, true);
	}
	
	private Button createButton(String text, boolean enabled)
	{
		Button button = new Button();
		button.setText(text);
		button.setEnabled(enabled);
		button.addClickListener(this);
		buttonsPanel.add(button);
		return button;
	}
	
	protected Widget getCellEditor(int row, int col)
	{
		return null;
	}

	protected void addRow() 
	{
		throw new UnsupportedOperationException();
	}
	
	protected void removeRow(int row)
	{
		throw new UnsupportedOperationException();
	}
	
	protected boolean canAddRow()
	{
		return false;
	}

	protected boolean canRemoveRow(int row)
	{
		return false;
	}

	public void onCellClicked(SourcesTableEvents sender, int row, int col)
	{
		if (!editEnabled) return;
		// translate to data row index
		row--;
		if (row<0) return;
		// handle row selection
		selectRow(row);
		// check if it's a double click on the same cell
		long time = System.currentTimeMillis();
		long millisSinceLastClick = time-lastClickTime;
		lastClickTime = time;
		if (clickedRow==row && clickedCol==col 
				&& millisSinceLastClick<DOUBLE_CLICK_MILLIS) 
		{
			onDoubleClick(row, col);
		}
		else {
			clickedRow = row;
			clickedCol = col;
		}
	}
	
	public void selectRow(int row)
	{
		if (row!=selectedRow) {
			if (selectedRow>=0) setRowSelected(selectedRow, false);
			if (row>=0) setRowSelected(row, true);
			selectedRow = row;
			refreshButtons();
		}		
	}
	
	private void onDoubleClick(int row, int col)
	{
		openEditor(row, col);
		refreshButtons();
	}
	
	private void setRowSelected(int row, boolean selected)
	{
		if (selected) {
			addRowStyle(row, Styles.SELECTED);
			removeButton.setEnabled(canRemoveRow(row));
			editors = new Widget[getColumnCount()];
		}
		else {
			closeEditor(row, false);
			removeRowStyle(row, Styles.SELECTED);
		}
	}

	public void onKeyDown(Widget sender, char keyCode, int modifiers) { }
	
	public void onKeyPress(Widget sender, char keyCode, int modifiers) { }
				
	private boolean closeEditor(int row, boolean validate)
	{
		boolean success = true;
		for (int col=0; col<getColumnCount(); col++) {
			try {
				closeEditor(row, col, validate);
			}
			catch (InvalidInputException e) {
				success = false;
			}
		}
		return success;
	}
	
	private void closeEditor(int row, int col, boolean validate) 
	throws InvalidInputException 
	{
		Widget editor = editors[col];
		if (editor!=null) {
			if (validate) {
				validateEditor(editor);
				setData(row, col);
			}
			refresh(row, col);
			editors[col] = null;
			editorCount--;
			if (editorCount==0 && validate) listeners.fireChange(this);
		}
	}
	
	private void openEditor(int row)
	{
		for (int col=getColumnCount()-1; col>=0; col--) {
			openEditor(row, col);
		}
	}

	private void openEditor(int row, int col)
	{
		if (editors[col]!=null) return;
		final Widget editor = getCellEditor(row, col);
		if (editor!=null) {
			editor.setSize("100%", "100%");
			setWidget(row, col, editor);
			if (editor instanceof HasFocus) ((HasFocus)editor).setFocus(true);
			editors[col] = editor;
			editorCount++;
		}		
	}
		
	public void onClick(Widget sender) 
	{
		if (!editEnabled) return;
		if (sender==addButton) {
			int row = getRowCount();
			addRow();
			refresh();
			if (getRowCount()==row+1) {
				selectRow(row);
				openEditor(row);
				newRowValidated = false;
			}
		}
		else if (sender==removeButton) {
			int row = selectedRow;
			selectRow(-1);
			removeRow(row);
			refresh();
		}
		else if (sender==validateButton) {
			if (closeEditor(selectedRow, true)) {
				if (!newRowValidated) newRowValidated = true;
			}
		}
		else if (sender==cancelButton) {
			int row = selectedRow;
			closeEditor(selectedRow, false);
			if (!newRowValidated) {
				selectRow(-1);
				removeRow(row);
				refresh();	
				newRowValidated = true;
			}
		}
		refreshButtons();
	}
	
	public void refresh()
	{
		super.refresh();
		refreshRowStyles();
	}
	
	void refreshRowStyles()
	{
		for (int row=0; row<getPhysicalRowCount(); row++) {
			if (editEnabled) addRowStyle(row, Styles.CLICKABLE);
			else removeRowStyle(row, Styles.CLICKABLE);
		}				
	}
	
	public void addChangeListener(ChangeListener listener)
	{
		listeners.add(listener);		
	}
	
	public void removeChangeListener(ChangeListener listener)
	{
		listeners.remove(listener);
	}
	
	public void validate() throws InvalidInputException
	{
		if (isBeingEdited()) {
			throw new InvalidInputException(Desktop.getDesktop().text.inputNotValidated());
		}
		else {
			if (selectedRow>=0 && !closeEditor(selectedRow, true)) {
				throw new InvalidInputException();
			}
		}
	}
	
	public void setEditEnabled(boolean enabled)
	{
		this.editEnabled = enabled;
		if (!enabled) {
			panel.remove(buttonsPanel);
			selectRow(-1);
		}
		else if (panel.getWidgetIndex(buttonsPanel)==-1) panel.add(buttonsPanel);
		refreshRowStyles();
	}
	
	void refreshButtons()
	{
		validateButton.setEnabled(editorCount>0);
		cancelButton.setEnabled(editorCount>0);
		addButton.setEnabled(editorCount==0 && canAddRow());
		removeButton.setEnabled(editorCount==0 && selectedRow!=-1 && canRemoveRow(selectedRow));
	}
	
	protected void validateEditor(Widget editor)
	throws InvalidInputException
	{
		if (editor instanceof Validatable) {
			((Validatable)editor).validate();
		}
	}
		
	abstract protected void setData(int row, int col);
	
	public boolean isBeingEdited()
	{
		return editorCount>0;		
	}
	
	public int getSelectedRow()
	{
		return selectedRow;
	}
	
	public boolean isEditorActive(Widget editor)
	{
		if (editors==null) return false;
		for (int i=0; i<editors.length; i++) {
			if (editors[i]==editor) return true;
		}
		return false;
	}
	
}
